/**
 *********************************************************
 ** <h1>SFMLex - Simple and Fast Multimedia Library Extensions.</h1>
 ** SFMLex Copyright (C) 2008 Neil Johnson (neil@emailus.me.uk)
 **
 ** SFML - Simple and Fast Multimedia Library
 ** Copyright (C) 2007-2008 Laurent Gomila (laurent.gom@gmail.com)
 **
 ** This software is provided 'as-is', without any express or implied warranty.
 ** In no event will the authors be held liable for any damages arising from the use of this software.
 **
 ** Permission is granted to anyone to use this software for any purpose,
 ** including commercial applications, and to alter it and redistribute it freely,
 ** subject to the following restrictions:
 **
 ** 1. The origin of this software must not be misrepresented;
 **    you must not claim that you wrote the original software.
 **    If you use this software in a product, an acknowledgment
 **    in the product documentation would be appreciated but is not required.
 **
 ** 2. Altered source versions must be plainly marked as such,
 **    and must not be misrepresented as being the original software.
 **
 ** 3. This notice may not be removed or altered from any source distribution.
 **
 ************************************************************
 */

#include "c_AnimSprite.h"
#include <assert.h>
#include <iostream>


namespace sfx
{
   /**
    * c_AnimSprite implementation
    */
   float c_AnimSprite::m_TimeDelta = 1.0f;

   const std::string c_AnimSprite::m_DefaultSequenceName = "Default";

   c_AnimSprite::c_AnimSprite( const sf::Image&   Image,
                               unsigned int       NumFrames,
                               e_AnimMode         Mode,
                               float              FPS,
                               unsigned int       FrameWidth,
                               unsigned int       FrameHeight,
                               const sf::Vector2f Position,
                               const sf::Vector2f Scale,
                               float              Roataion,
                               const sf::Color    Color     
                             ) :
   sf::Sprite( Image,
               Position,
               Scale,
               Roataion,
               Color )
   {

      if( FrameHeight == 0 )
      {
         FrameHeight = FrameWidth;
      }
      
      if( generateFrames( NumFrames, FrameWidth, FrameHeight ) )
      {
         //Build initial incrementing frame sequence
         std::vector< unsigned int > Sequence;
         Sequence.reserve( NumFrames );
         for( unsigned int Idx = 0; Idx < NumFrames; Idx++ )
         {
            Sequence.push_back( Idx );
         }
         
         assert( Sequence.size() == NumFrames );

         c_AnimSequence* AnimSequence = new c_AnimSequence( this, 
                                                            m_DefaultSequenceName,
                                                            Mode,
                                                            FPS,
                                                            Sequence );
         assert( AnimSequence != 0 );

         addSequence( m_DefaultSequenceName, AnimSequence );
         
         setCurrentSequence( m_DefaultSequenceName );
      }

   }
      
   bool c_AnimSprite::generateFrames( unsigned int FrameCount, unsigned int FrameWidth, unsigned int FrameHeight )
   {
      
      if( FrameHeight == 0 )
      {
         FrameHeight = FrameWidth;
      }
      
      bool Success( false );

      clearFrames();
      
      if( FrameHeight &&
          FrameWidth  &&
          FrameCount     )
      {
         const sf::Image* Image = GetImage();
         if( Image )
         {
            const int ImageWidth = Image->GetWidth();
            if( ImageWidth )
            {
               const int Span = ImageWidth / (int)FrameWidth;

               for( unsigned int FrameNumber = 0; FrameNumber < FrameCount; FrameNumber++ )
               {
                  const int Row    = FrameNumber / Span;
                  const int Column =  FrameNumber % Span;
                  
                  const int LeftCord   = Column * FrameWidth;
                  const int TopCord    = Row    * FrameHeight;
                  const int RightCord  = LeftCord + FrameWidth;
                  const int BottomCord = TopCord  + FrameHeight;

                  sf::IntRect* SubRec = new sf::IntRect( LeftCord, TopCord, RightCord, BottomCord );
                  assert( SubRec != 0 );

                  m_Frames.push_back( SubRec );
               }
               
               Success = true;
            }
         }
      }
      
      return Success;
   }

   void c_AnimSprite::clearFrames( void )
   {
      std::vector< sf::IntRect* >::iterator it;

      for( it = m_Frames.begin(); it != m_Frames.end(); ++it )
      {
         sf::IntRect* Rec;
         Rec = *it;
         delete Rec;
      }
      m_Frames.clear();
   }
   
   bool c_AnimSprite::createSequence( const std::string&           Name, 
                                      e_AnimMode                   Mode,
                                      float                        FPS,
                                      std::vector< unsigned int >& Sequence )
   {
      if( m_Frames.size() == 0 )
      {
         return false;
      }

      const unsigned int MaxFrameNumber = m_Frames.size() - 1;

      //Check to see if supplied Sequence is within range
      std::vector< unsigned int >::iterator it;
      for( it = Sequence.begin(); it != Sequence.end(); ++it )
      {
         if( *it > MaxFrameNumber )
         {
            //It's not
            return false;
         }
      }
      
      c_AnimSequence* AnimSequence = new c_AnimSequence( this, 
                                                         Name,
                                                         Mode,
                                                         FPS,
                                                         Sequence );
      assert( AnimSequence != 0 );
      if( AnimSequence == 0 )
      {
         return false;
      }
      addSequence( Name, AnimSequence );

      return true;
   }

   void c_AnimSprite::setCurrentFrame( unsigned int FrameNumber )
   {
      assert( FrameNumber < m_Frames.size() );
     
      this->SetSubRect( *m_Frames.at( FrameNumber ) );
   }
   
   void c_AnimSprite::setCurrentSequence( const std::string& SequenceName )
   {
      t_AnimSequenceMap::iterator it;
      if( m_AnimSequenceMap.end() != ( it = m_AnimSequenceMap.find( SequenceName ) ) )
      {
         m_CurrentSequence = it->second;
      }
      else
      {
         std::cerr << "c_AnimSprite::setCurrentSequence(): " << SequenceName << " is not a valid sequence name\n";
      }
         
   }
   
   void c_AnimSprite::addSequence( const std::string& SequenceName, c_AnimSequence* Sequence )
   {
#ifdef _DEBUG
      //Check that this name is not already used
      assert( m_AnimSequenceMap.end() == m_AnimSequenceMap.find( SequenceName ) );
#endif //_DEBUG
      m_AnimSequenceMap.insert( make_pair( SequenceName, Sequence ) );
   }

   c_AnimSprite::~c_AnimSprite(void)
   {
      clearFrames();
   }

}



namespace sfx
{
   /**
    * c_AnimSprite implementation
    */
   c_AnimSprite::c_AnimSequence::c_AnimSequence( c_AnimSprite*                      AnimSprite,
                                                 const std::string&                 Name,
                                                 c_AnimSprite::e_AnimMode           Mode,
                                                 float                              FPS,
                                                 const std::vector< unsigned int >& Sequence ) :
      m_AnimSprite( AnimSprite ),
      m_Name( Name ),
      m_Mode( Mode ),
      m_FPS( FPS ),
      m_FrameSequence( Sequence )
   {
      initSequence();

      m_AnimSprite->setCurrentFrame( m_CurrentFrame );
   }
   
      void c_AnimSprite::c_AnimSequence::initSequence( void )
   {
      assert( m_FrameSequence.size() );

      //Set initial frames
      switch( m_Mode )
      {
      case c_AnimSprite::d_ANIM_MODE_NONE:
         {
            m_CurrentFrame     = 0;
            m_FrameSequenceDir = 0;

            break;
         }

      case c_AnimSprite::d_ANIM_MODE_FWD_LOOP:
         {
            m_CurrentFrame     = 0;
            m_FrameSequenceDir = 1;

            break;
         }

      case c_AnimSprite::d_ANIM_MODE_REV_LOOP:
         {
            m_CurrentFrame     = m_FrameSequence.size() - 1;
            m_FrameSequenceDir = -1;

            break;
         }

      default:
         {
            assert(0);
            break;
         }
      }
      
      if( m_FPS < 0.00001f )
      {
         m_FPS = 0.00001f;
      }
      m_FrameTime        = 1.0f / m_FPS;
      m_CurrentFrameTime = m_FrameTime;
   }

      void c_AnimSprite::c_AnimSequence::update( float UpdateDelta )
   {
      if( UpdateDelta > m_CurrentFrameTime )
      {
         const int LastFrameNumber = m_FrameSequence.size() - 1;

         float Tmp = UpdateDelta - m_CurrentFrameTime;

         int FrameDelta = (int)( 1.0f + (Tmp / m_FrameTime ) );

         m_CurrentFrame += ( FrameDelta * m_FrameSequenceDir );
         
         
         //Check if gone out of bounds
         if( m_CurrentFrame < 0 )
         {
            const int TmpDelta = abs( m_CurrentFrame );
            m_CurrentFrame = TmpDelta == 1 ? LastFrameNumber : ( LastFrameNumber - ( TmpDelta % LastFrameNumber  ) );
         }
         else if( m_CurrentFrame > LastFrameNumber )
         {
            const int TmpDelta = m_CurrentFrame - LastFrameNumber;

            m_CurrentFrame = TmpDelta == 1 ? 0 : TmpDelta % LastFrameNumber;
         }

         m_CurrentFrameTime = m_FrameTime;
         
         m_AnimSprite->setCurrentFrame( m_CurrentFrame );
      }
      else
      {
         m_CurrentFrameTime -= m_TimeDelta;
      }
   }

}
